import signer.RSA2Signer;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * @author 51675
 */


public class Transaction {

    private static final int VERSION = 1;
    private List<Input> inputs;
    private List<Cost> costs;
    private byte[] hash;
    private byte[] ptz;
    private byte[] pubKey;


    /**
     * 交易的费用 vin和vout的差值为交易费
     */
    private int fee;

    /**
     * Timelocks 交易锁定时长
     */
    private int timeLocks;

    /**
     * 接受区块之后回写这几个字段
     */
    private int height;
    private int size;
    private int time;


    public Transaction() {
    }

    public Transaction(byte[] hash) {
        this.hash = hash;
    }


    public List<Cost> getCosts() {
        return costs;
    }

    public void setCosts(List<Cost> costs) {
        this.costs = costs;
    }

    public byte[] getPtz() {
        return ptz;
    }

    public void setPtz(byte[] ptz) {
        this.ptz = ptz;
    }


    public byte[] serialize(Wallet wallet) throws Exception {

        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
        DataOutputStream out = new DataOutputStream(byteOutputStream);
        out.writeInt(VERSION);

        int vin = 0;
        int vout = 0;

        byte[] publicKey = wallet.getPublicKeyByte();
        String address = Address.convertPubKeyToAddress(publicKey);

        if (inputs != null) {
            out.writeInt(inputs.size());
            for (Input input : inputs) {
                out.write(input.getTransactionId().getHash());
                out.writeInt(input.getAmount());
            }
        } else {
            out.writeInt(0);
            out.writeInt(ptz.length);
            out.write(ptz);
        }

        out.writeInt(costs.size());
        for (Cost cost : costs) {
            byte[] addr = cost.getAddress().getBytes();
            out.writeByte(addr.length);
            out.write(addr);
            out.writeInt(cost.getAmount());
        }

        int length = publicKey.length;
        out.writeInt(length);
        out.write(publicKey);
        out.flush();

        byte[] privateKey = wallet.getPrivateKeyByte();
        byte[] signer = new RSA2Signer().doSign(byteOutputStream.toByteArray(), privateKey);
        out.write(signer);
        out.flush();

        byte[] content = byteOutputStream.toByteArray();
        Transaction transaction = new Transaction();
        boolean result = check(content, transaction);
        if (!result) {
            throw new Exception("签名验证失败");
        }
        System.out.println(result + "支付订单创建成功");
        return content;
    }


    public static boolean check(byte[] content, Transaction transactionNew) throws Exception {

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(content);
        DataInputStream inputStream = new DataInputStream(byteArrayInputStream);

        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
        DataOutputStream out = new DataOutputStream(byteOutputStream);

        int inv = 0;
        int outv = 0;

        int total = 0;

        int version = inputStream.readInt();
        out.writeInt(version);
        total += 4;


        int inSize = inputStream.readInt();
        out.writeInt(inSize);
        total += 4;

        if (inSize > 0) {

            List<Input> inputs = new ArrayList<>();
            for (int i = 0; i < inSize; i++) {
                byte[] hash = new byte[32];
                inputStream.read(hash);
                int amount = inputStream.readInt();
                if (amount <= 0) {
                    return false;
                }
                inv += amount;
                total += 36;
                out.write(hash);
                out.writeInt(amount);
                inputs.add(new Input(hash, amount));
            }

            transactionNew.setInputs(inputs);
        } else {
            int ptzLength = inputStream.readInt();
            total += 4;
            out.writeInt(ptzLength);

            byte[] ptz = new byte[ptzLength];
            inputStream.read(ptz);
            total += ptzLength;
            out.write(ptz);

            transactionNew.setPtz(ptz);
        }


        int outsize = inputStream.readInt();
        total += 4;
        out.writeInt(outsize);

        List<Cost> costList = new ArrayList<>();

        for (int i = 0; i < outsize; i++) {
            int addrLen = inputStream.readByte();
            byte[] address = new byte[addrLen];
            inputStream.read(address);
            int amount = inputStream.readInt();
            outv += amount;
            total += (1 + addrLen + 4);
            out.writeByte(addrLen);
            out.write(address);
            out.writeInt(amount);

            if (amount <= 0) {
                return false;
            }

            Cost cost = new Cost();
            cost.setAddress(new String(address));
            cost.setAmount(amount);
            costList.add(cost);
        }

        transactionNew.setCosts(costList);

        if (inSize > 0) {
            if (inv != outv) {
                System.out.println("input金额和output金额不匹配");
            }
        } else {
            // CoinBase  检查outv是否为相应block的打包收益
        }


        int length = inputStream.readInt();
        total += 4;
        out.writeInt(length);

        byte[] publicKey = new byte[length];
        inputStream.read(publicKey);
        total += length;
        out.write(publicKey);
        transactionNew.setPubKey(publicKey);

        int signerLen = content.length - total;
        byte[] signer = new byte[signerLen];
        inputStream.read(signer);

        out.flush();
        byte[] content1 = byteOutputStream.toByteArray();
        boolean v = new RSA2Signer().doVerify(content1, publicKey, signer);

        if (!v) {
            transactionNew = null;
        }


        byte[] bytes = Hash256.hash(content);
        transactionNew.setSize(content.length);
        transactionNew.setHash(bytes);
        return v;
    }


    public int getMine(byte[] pubKey) throws Exception {

        for (Cost cost : costs) {
            if (Address.convertPubKeyToAddress(pubKey).equals(cost.getAddress())) {
                return cost.getAmount();
            }
        }

        return -1;
    }


    public byte[] getHash() {
        return hash;
    }

    public void setHash(byte[] hash) {
        this.hash = hash;
    }

    public byte[] getPubKey() {
        return pubKey;
    }

    public void setPubKey(byte[] pubKey) {
        this.pubKey = pubKey;
    }

    public List<Input> getInputs() {
        return inputs;
    }

    public void setInputs(List<Input> inputs) {
        this.inputs = inputs;
    }

    public int getSize() {
        if (size == 0) {
            throw new Error("Transaction----size不应该为0");
        }
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }


    public int getMine(String address) {
        for (Cost cost : costs) {
            if (cost.getAddress().equals(address)) {
                return cost.getAmount();
            }
        }
        return 0;
    }


    /**
     * 获取交易信息
     */
    public String getTransactionInfo(String address) {
        StringBuilder sb = new StringBuilder();
        sb.append("Height=" + height);
        sb.append(",time=" + DateFormatUtil.timeFormat(time));
        sb.append(",detail=");
        sb.append(detail(address));
        return sb.toString();
    }


    private String detail(String address) {

        StringBuilder sb = new StringBuilder();
        sb.append("[");

        String inputAddress = Address.convertPubKeyToAddress(pubKey);
        if (inputs == null) {
            sb.append("CoinBase,");
        }

        sb.append(inputAddress);
        sb.append("->");


        for (Cost cost : costs) {
            sb.append(cost.getAddress());
            if (cost.getAddress().equals(address)) {
                sb.append("=mine");
            }
            sb.append("(").append(cost.getAmount()).append(")").append(",");
        }

        sb.deleteCharAt(sb.length() - 1);
        sb.append("]");

        return sb.toString();
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getTime() {
        return time;
    }

    public void setTime(int time) {
        this.time = time;
    }


    public boolean isCoinBase() {
        return inputs == null || inputs.size() == 0;
    }
}